前情提要:昨天介紹完了的三個方法,我們知道如何透過getState取得state;知道如何透過dispatch執行action更新state;知道怎麼透過subscribe來監聽state。
Redux本身就和框架無關。我們可以使用在原生javascript、Angular或React。
有些綁定可以將Redux和我們所使用的framework或library綁在一起。
對React來說這個角色就是react-redux
react-redux組成要點有兩個,分別為:
而connect這個function有兩個參數,這兩個參數都是非必需的,分別為:
每次
都會被調用,mapStateToProps會收到整個store的state,必須返回一個object作為props給component使用,而object的內容呢則為我們需要用到的state。好吧,相信大家都還不太清楚到底發生了什麼事。
接下來我們透過改寫todos帶大家使用react-redux吧
繼續之前,我先把todos拉一個branch出來安裝react-redux和redux
yarn add redux react-redux
然後把專案目錄下的index.js的App component獨立成一個檔案放在src/components下(其實早該獨立了...),並且在專案目錄下的index.js匯入app.js,它就會變得很清爽,如下:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './src/components/app';
import './src/styles/index.scss';
ReactDOM.render(<App/>, document.getElementById('app'));
前置作業完成後,接著開始進入改寫囉~
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux"; // 1.
import store from "./src/store"; // 2.
import App from "./src/components/app";
import "./src/styles/index.scss";
ReactDOM.render(
<Provider store={store}> { /* 將store作為props傳遞給其他component */ }
<App />
</Provider>,
document.getElementById("app")
);
store這邊處理相對單純,生成store丟去給專案目錄下的index.js用就好了
import { createStore } from "redux";
import rootReducer from "../reducers"; // 後面沒指定檔案名,預設直接抓index.js
const store = createStore(rootReducer);
export default store;
在這個地方停下腳步稍微想一下,todos的內容到底是什麼?!
對了,我們從頭到尾操控的都是todoList array,所以我們預設todoList的初始值是個空array。
確定state的內容後,我們打開src/reducers/index.js,回想昨天內容:
當應用程序(application)越來越大時,reducer也會越來越胖,我們可以藉由把reducer拆分成多個function,然後透過
combineReducers
把它們結合。
做到這裡,覺得狀態管理就是一門分門別類的藝術,module的功能就是讓我每個動作拆分越細越好,所以我們來使用combineReducers吧!
import { combineReducers } from 'redux'; // 1.
import todoListReducer from './todo_list_reducer'; // 3.
const rootReducer = combineReducers({
todoList: todoListReducer // 2.
});
export default rootReducer;
接著處理todoListReducer
import { ADD_NEW_TODO, COMPLETE_TODO, REMOVE_TODO } from '../constants/action_type';
import update from 'immutability-helper';
export default function(state = [], action) {
switch (action.type) {
case ADD_NEW_TODO:
return [ ...state, action.newItem ];
case COMPLETE_TODO:
return update(state, {
[action.index]: {$set: action.item}
});
case REMOVE_TODO:
return update(state, {
$splice: [[action.index, 1]]
});
default:
return state;
}
};
action_type的任務非常簡單,就是提供reducer和action共用的常數設定
export const ADD_NEW_TODO = "ADD_NEW_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";
export const REMOVE_TODO = "REMOVE_TODO";
再來處理action creators,這裡使用camelCase定義action的method,並回傳一個obejct,內含type和我們自訂的data
import uuid from 'uuid/v4';
import { ADD_NEW_TODO, COMPLETE_TODO, REMOVE_TODO } from '../constants/action_type';
export const addNewTodo = text => {
return {
type: ADD_NEW_TODO,
newItem: {
text,
key: uuid(),
completed: false
}
}
}
export const completeTodo = (index, item) => {
return {
type: COMPLETE_TODO,
index, item
}
}
export const removeTodo = (index) => {
return {
type: REMOVE_TODO,
index
};
}
Redux的資料都處理得差不多了,接下來調整我們的new_todo.js吧!
這邊改動的幅度相對小,主要只是把action方法匯入,並透過connect dispatch(發送) action。
因為在這裡我們不需要store的state資料,所以第一個參數給它null就可以了。
值得一提的是第二個參數mapDispatchToProps。稍早提到mapDispatchToProps可以為object,其實它的做法就是把第二個參數作為包著action的object丟出去就可以變成props給component調用,在這邊我們需要調用的action就是addNewTodo,所以把它匯出就可以了~
備註:這裡保留component內部的state設定,因為其他component並不需要輸入過程中的value,不需要放到store共用。
import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { addNewTodo } from "../actions";
class NewTodo extends PureComponent {
// .....
}
export default connect(null, { addNewTodo })(NewTodo);
之前我們將ul element放在app component內,這次想調整一下todo_list的架構,將todo_list改為一整個ul element,而li的部分另外再拆一個todo_list_item渲染array內容,這樣做的原因是我想要保持component乾淨
在這裡我們透過mapStateToProps取得todoList資料;透過mapDispatchToProps取得action creators,並作為props傳給TodoListItem使用
import React from 'react';
import { connect } from 'react-redux';
import { completeTodo, removeTodo } from '../actions/';
import TodoListItem from './todo_list_item';
const TodoList = ({todo, completeTodo, removeTodo}) => {
return todo.length ? (
<ul className="list">
<TodoListItem
todo={todo}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
</ul>
): null
};
const mapStateToProps = ({todoList}) => ({
todo: todoList
})
const mapDispatchToProps = {
completeTodo, removeTodo
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
這邊的TodoListItem所有的資料都是從TodoList透過props送過來。
import React from 'react';
const TodoListItem = ({todo, completeTodo, removeTodo}) => (
todo.map((item, index) => (
<li key={item.key} className="list-item">
<input
type="checkbox"
id={item.key}
className="list-item-complete"
onChange={() => completeTodo(index, { ...item, completed: !item.completed})}
/>
<label htmlFor={item.key}>{item.text}</label>
<button className="btn" onClick={() => removeTodo(index)}>移除</button>
</li>
))
);
export default TodoListItem;
最後,我們可以把App component瘦身了。
儲存todoList的array可移除;新增、標記和刪除todoList的方法可移除。
接著把沒有method和local state的class component轉成function component。
調整後的程式碼如下:
import React from 'react';
import NewTodo from './new_todo';
import TodoList from './todo_list';
const App = () => {
return (
<div className="main">
<h1 className="title">todos</h1>
<NewTodo/>
<TodoList/>
</div>
);
}
export default App;
今日總結:
今天結合redux改寫todos,不得不說即使是個渣渣todos,改寫起來也是蠻討厭的。
就像舊家搬新家一樣,房間格局不一樣又要再整理一次。如果有預期application未來會成長茁壯,一開始就使用redux會比事後調整開心許多!